package org.nViron.nvGraffiti;

import instar.dg.Categories.Region;
import instar.dg.Categories.RegionListener;
import instar.dg.Client.BaseClient;
import instar.dg.Client.ClientListener;
import instar.dg.Common.Error;
import instar.dg.Common.ID;
import instar.dg.Common.Position;
import instar.dg.Common.PositionListener;
import instar.dg.Common.QualifiedPosition;
import instar.dg.Debug.Debug;
import instar.dg.GraffitiCache.GraffitiCache;
import instar.dg.GraffitiCache.GraffitiCacheListener;
import instar.dg.GraffitiCache.PersistentCache;
import instar.dg.GraffitiCache.PersistentCacheMemory;
import instar.dg.Graffito.GraffitoBody;
import instar.dg.Graffito.GraffitoHeader;
import instar.dg.Packets.Packet;
import instar.dg.Packets.PacketListener;
import instar.dg.Peer.Ticket;
import instar.dg.Peer.TicketTimeoutListener;
import instar.dg.Positioning.PositionProvider;
import instar.dg.Profile.AddressBookListener;
import instar.dg.Profile.Friend;
import instar.dg.Profile.LoadPersonListener;
import instar.dg.Profile.Person;
import instar.dg.Profile.Selection;
import instar.dg.Protocol.Messages.WLANPosAvailable;
import instar.dg.Protocol.Requests.LoginRequest;
import instar.dg.SpatialCoding.SpatialNameList;
import instar.dg.Tiles.TilesCache;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import org.nViron.nvCommon.nvPosition;
import org.nViron.nvConfig.nvConfig;
import org.nViron.nvData.nvDataSource;
import org.nViron.nvData.nvDataSourceMgr;
import org.nViron.nvParams.nvContextParamMgr.CONTEXTPARAM;
import org.nViron.nvRender.nvRenderer;

/**
 * DataSource implementation that encapsulates the Digital Graffiti Service connection and data handling
 * @author Roland Haring <p>
 * <script type="text/javascript" src="http://www.ohloh.net/p/376434/widgets/project_basic_stats.js"></script>
 */
public class nvGraffitiSource extends nvDataSource {

    class AddressbookFacade implements AddressBookListener {

        public void incomingFriendRequest(final ID id, final String msg) {
            try {
                baseclient.getMe().getProfile().getAddressBook().get(id, new LoadPersonListener() {

                    public void onPersonLoaded(Person p) {
                    }
                });
            } catch (Exception ex) {
            }
        }

        public void onPersonLoaded(Person p) {
        }

        public void onPersonSearch(String searchValue, Person[] results) {
        }
    }

    class ClientFacade implements ClientListener {

        public void onClientUpdateNotification(String msg, String updateUrl) {
        }

        public void onError(Error error) {
            //#debug
            Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] GraffitiClient.onError");
            Debug.out.println("Error " + error.getErrorMessage());
        }

        public void onGraffitoCreation(GraffitoHeader header, GraffitoBody body) {
        }

        public void onIncomingBody(GraffitoHeader header, GraffitoBody body) {
        }

        public void onStateChanged(byte oldState, byte newState) {
            //#debug
            long before = 0;
            //#debug
            Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] onStateChanged( " + oldState + ", " + newState + ")");
            switch (newState) {
                case BaseClient.CONNECTED:
                    //#debug
                    before = System.currentTimeMillis();
                    //#debug
                    Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] GraffitiClient.ClientFacade.onStateChanged( , CONNECTED) took " + (System.currentTimeMillis() - before) + " ms");
                    break;

                case BaseClient.READY:
                    //#debug
                    before = System.currentTimeMillis();
                    TilesCache.getInstance().setUserID(baseclient.getMe().getId());
                    try {
                        // TODO load addressbook
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }

                    //#debug
                    Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] GraffitiClient.ClientFacade.onStateChanged( , READY) took " + (System.currentTimeMillis() - before) + " ms");
                    break;

                case BaseClient.OFFLINE:
                    //#debug
                    before = System.currentTimeMillis();
                    if (oldState == BaseClient.READY) {
                        try {
                            // TODO save addressbook
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    } else if (oldState == BaseClient.STARTED) {
                    }
                    if (baseclient.isConnectionBrokenByDisconnectFromServer()) {
                    } else {
                    }
                    //#debug
                    Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] GraffitiClient.ClientFacade.onStateChanged( , OFFLINE) took " + (System.currentTimeMillis() - before) + " ms");
                    break;
            }
        }

        public void positionSystemChanged(PositionProvider oldSystem, PositionProvider newSystem) {
        }

        public void spatialNameReply(SpatialNameList list) {
        }

        public void WLANPositioningAvailable(WLANPosAvailable msg) {
        }
    }
    class GraffitiFacade implements GraffitiCacheListener {

        private nvGraffitiSource source;

        public GraffitiFacade(nvGraffitiSource source) {
            this.source = source;
        }

        public void addGraffito(GraffitoHeader graffito) {
            Debug.out.println("adding graffiti " + graffito.toString());
            nvGraffitoEntity entity = new nvGraffitoEntity(getDataSourceMgr().getSimulator().getEntityMgr(), graffito, source);
            getDataSourceMgr().getSimulator().getDataEntityMgr().addDataEntity(entity);
            add2IdMap(entity.getGraffitoID(), entity.getId());
        }

        public void bodyLoaded(GraffitoBody body) {
        }

        public void popupGraffito(final GraffitoHeader graffito) {
            if (baseclient.getPositionProvider() == null || baseclient.getPositionProvider().getCurrentPosition() == null || (baseclient.getPositionProvider().getCurrentPosition() instanceof QualifiedPosition && ((QualifiedPosition) baseclient.getPositionProvider().getCurrentPosition()).getFloor() == graffito.getFloor())) {

                graffito.setPopupAlreadyShown(true);
            }
        }

        public void removeGraffito(GraffitoHeader graffito, boolean deleted) {
            // TODO what is the deleted parameter used for?
            Debug.out.println("removing graffiti " + graffito.toString());
            try {
                int idInt = ID2id(graffito.getId());
                getDataSourceMgr().getSimulator().getDataEntityMgr().removeDataEntity(idInt);
                removeId(idInt);
            } catch (NullPointerException ex) {
            }
        }

        public void updateGraffito(GraffitoHeader graffito) {
            try {
                removeGraffito(graffito, true);
                addGraffito(graffito);
            } catch (NullPointerException ex) {
            }
        }
    }
    class PacketListenerImpl implements PacketListener {

        public void incomingPacket(Packet packet) {

        }

        public void sentPacket(Packet packet) {
           
        }
    }

    class PositionListenerImpl implements PositionListener {
    	
            public void onPositionChanged(Position _old, Position _new) {
                // TODO do something
            }
        }
    class RegionListenerImpl implements RegionListener {

        public void regionLogin(Region rgn) {
        }

        public void regionLogout(Region rgn) {

        }

        public void regionUpdate(Region rgn) {
            Selection selection = baseclient.getMe().getProfile().getSelection(rgn.getId().getIdA());
            if (selection == null) {
                // create a selection for the region
                selection = new Selection();
                selection.setRegionServerId(rgn.getId().getIdA());
            }
            selection.setRootCategoryID(new ID(ID.REGION_SERVER, rgn.getId().getIdA(), 0));
            selection.addSelection(selection.getRootCategoryID());
//            Enumeration e = rgn.getCategories();
//            while (e.hasMoreElements()) {
//                Category c = (Category)e.nextElement();
//                selection.addSelection(c.getId());
//            }
            baseclient.getMe().getProfile().setSelection(selection);
            baseclient.getMe().getProfile().requestChangeSelection();
        }
    }
    class TimeoutFacade implements TicketTimeoutListener {

        public void ticketTimedOut(Ticket ticket) {
            //#debug
            Debug.out.println(System.currentTimeMillis() + " ms - [" + Thread.currentThread().getName() + "] ticket " + ticket.getTicketId() + " timed out!!");
            if (ticket.getMessage().getType() == LoginRequest.MessageTypeIDs.LOGIN_REQUEST) {
            }
        }
    }
    public static final byte NO_ERROR = 0x00;
    private nvGraffitiClient baseclient;

    private nvGraffitiSourceConfig config;
    private ID userID;

    private ClientFacade clientfacade = new ClientFacade();

    private PositionListener positionListener = new PositionListenerImpl();

    private RegionListener regionListener = new RegionListenerImpl();

    private PacketListener packetListener = new PacketListenerImpl();

    private GraffitiFacade graffitiFacade = new GraffitiFacade(this);

    private final HashMap id2IDMap = new HashMap();

    private final HashMap ID2idMap = new HashMap();

    private final ArrayList listener = new ArrayList();

    public nvGraffitiSource(nvDataSourceMgr dataSourceMgr) {
        super(dataSourceMgr);
        nvConfig _config = dataSourceMgr.getSimulator().getConfig();
        config = (nvGraffitiSourceConfig)_config;
        baseclient = new nvGraffitiClient(this, _config);
        baseclient.addClientListener(clientfacade);
        baseclient.addPositionListener(positionListener);
        baseclient.addRegionListener(regionListener);
        baseclient.getProviderPeer().addPacketListener(packetListener);

        PersistentCache persistent = null;
        if (config.isUsePersistentGraffitiCache()) {
            try {
                persistent = new nvPersistentCacheSE(baseclient, config.getCachePath(), config.getCacheNumberOfHeader(), config.getCacheNumberOfBodies());

            } catch (Exception ex) {
                ex.printStackTrace();
            }
        } else {
            persistent = new PersistentCacheMemory(baseclient, config.getCacheNumberOfHeader(), config.getCacheNumberOfBodies());
        }
        baseclient.setGraffitiCache(new GraffitiCache(baseclient, persistent));
        baseclient.getGraffitiCache().addListener(graffitiFacade);

        baseclient.getProviderPeer().getTicketManager().addListener(new TimeoutFacade());
        baseclient.getMe().getProfile().getAddressBook().addAddressBookListener(new AddressbookFacade());

        baseclient.getGraffitiCache().checkGraffitiInRange();
    }

    protected void add2IdMap(ID idID, int idInt) {
        id2IDMap.put(new Integer(idInt), idID);
        ID2idMap.put(idID, new Integer(idInt));
    }

    public void addFriend(Friend f) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    public void addGraffito(GraffitoHeader gh) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    public synchronized void addListener(nvGraffitiSourceListener gsl) {
        listener.add(gsl);
    }

    @Override
    public boolean doesProvide(CONTEXTPARAM contextParam) {
        switch (contextParam) {
            case MIME_TYPE:
            case POPULARITY:
            case RATING:
            case READ_FREQ:
            case USER_RATING:
            case DELTA_V:
            case REGIONALITY:
            case SELECTION_SIMILARITY:
                return false;
        }
        return true;
    }

    @Override
    protected void finalize() throws Throwable {
        baseclient.stop();
        super.finalize();
    }

    public nvGraffitiClient getBaseclient() {
        return baseclient;
    }

    public nvGraffitiSourceConfig getConfig() {
        return config;
    }

    public ID getID() {
        return this.userID;
    }

    public ID getUserID() {
        return userID;
    }

    protected ID id2ID(int id) throws NullPointerException {
        return (ID)id2IDMap.get(new Integer(id));
    }

    protected int ID2id(ID id) throws NullPointerException {
        return ((Integer)ID2idMap.get(id)).intValue();
    }

    @Override
    public boolean init() {
        if (baseclient.connect(config.getProviderIP(), config.getProviderPort()) != NO_ERROR) {
            return false;
        }
        baseclient.doLogin("roland", "roland");
        return true;
    }

    public synchronized boolean isListenerEmpty() {
        return listener.isEmpty();
    }

    public void remove(ID id) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    protected void removeId(ID idID) {
        int idInt = ID2id(idID);
        id2IDMap.remove(new Integer(idInt));
        ID2idMap.remove(idID);
    }

    protected void removeId(int idInt) {
        ID idID = id2ID(idInt);
        id2IDMap.remove(new Integer(idInt));
        ID2idMap.remove(idID);
    }

    public synchronized boolean removeListener(nvGraffitiSourceListener gsl) {
        return listener.remove(gsl);
    }

    public void render(nvRenderer renderer) {
    }

    public synchronized void setID(ID id) {
        this.userID = id;
        // TODO use event listener here
        for (Iterator i = listener.iterator(); i.hasNext(); ) {
            nvGraffitiSourceListener gsl = (nvGraffitiSourceListener)i.next();
            gsl.setUserID(id);
        }
    }

    public void update(float timeStep) {
    }

    @Override
    public void updateCameraPos(nvPosition cameraPosition) {
        baseclient.setPerceptualPosition(cameraPosition.getLongitude(), cameraPosition.getLatitude(), cameraPosition.getAltitude());
    }

    @Override
    public void updateUserPos(nvPosition userPosition) {
        baseclient.setPosition(userPosition.getLongitude(), userPosition.getLatitude(), userPosition.getAltitude());
    }
}
